﻿using System;
using Newtonsoft.Json.Linq;
using TrinityAPI.Base.Tools;

namespace TrinityAPI.Base.Models
{
    public class LinqValue
    {
        private JToken _value;

        public LinqValue(JToken value)
        {
            this._value = value;
        }

        public LinqValue this[int key]
        {
            get
            {
                if (_value == null || _value.Type != JTokenType.Array)
                    return new LinqValue(null);
                return new LinqValue(_value[key]);
            }
        }

        public LinqValue this[string key]
        {
            get
            {
                if (_value == null || _value.Type != JTokenType.Object)
                    return new LinqValue(null);
                return new LinqValue(_value[key]);
            }
        }

        public U Value<U>(U defaultValue = default(U))
        {
            try
            {
                JToken value = this._value;
                if (value == null)
                    return defaultValue;

                if (value is U)
                    return (U)(object)value;

                if (value is JValue)
                {
                    JValue jvalue = value as JValue;
                    if (jvalue.Value == null)
                        return defaultValue;
                    if (jvalue.Value is U)
                        return (U)jvalue.Value;

                    Type targetType = typeof(U);
                    if (ConvertUtils.IsNullableType(targetType))
                    {
                        if (jvalue.Value == null)
                            return defaultValue;

                        targetType = Nullable.GetUnderlyingType(targetType);
                    }

                    return (U)System.Convert.ChangeType(jvalue.Value, targetType, System.Globalization.CultureInfo.InvariantCulture);
                }
            }
            catch
            { }
            return defaultValue;
        }

        private static bool isNull(object v)
        {
            if (v == null)
                return true;

            if (v is JToken && ((JToken)v).Type == JTokenType.Null)
                return true;

            if (v is LinqValue && (((LinqValue)v)._value == null || ((LinqValue)v)._value.Type == JTokenType.Null))
                return true;

            return false;
        }

        private static bool Equals(LinqValue a, object b)
        {
            bool aIsNull = isNull(a);
            bool bIsNull = isNull(b);
            if (aIsNull && bIsNull)
                return true;
            else if (aIsNull || bIsNull)
                return false;

            if (!(a._value is JValue))
                return false;

            if (b is JValue)
                b = (b as JValue).Value;
            else if (b is LinqValue)
            {
                if (!((b as LinqValue)._value is JValue))
                    return false;
                b = ((b as LinqValue)._value as JValue).Value;
            }

            JValue value = a._value as JValue;

            if (value.Type == JTokenType.Integer && ConvertUtils.IsInteger(b))
                return value.Value<long>() == Convert.ToInt64(b);
            else if ((value.Type == JTokenType.Integer || value.Type == JTokenType.Float) && ConvertUtils.IsNumber(b))
                return value.Value<decimal>() == Convert.ToDecimal(b);
            else if (value.Type == JTokenType.Date && b is DateTime)
                return value.Value<DateTime>() == (DateTime)b;
            else if (value.Type == JTokenType.TimeSpan && b is TimeSpan)
                return value.Value<TimeSpan>() == (TimeSpan)b;
            else if (value.Type == JTokenType.String && b is String)
                return value.Value<string>() == b as string;
            else if (value.Type == JTokenType.Boolean && b is Boolean)
                return value.Value<bool>() == (Boolean)b;
            else if (value.Type == JTokenType.Guid)
                return value.Value<string>() == b.ToString();
            else
                return false;
        }

        private static bool GreaterThen(LinqValue a, object b)
        {
            bool aIsNull = isNull(a);
            bool bIsNull = isNull(b);
            if (aIsNull || bIsNull)
                return false;

            if (!(a._value is JValue))
                return false;

            if (b is JValue)
                b = (b as JValue).Value;
            else if (b is LinqValue)
            {
                if (!((b as LinqValue)._value is JValue))
                    return false;
                b = ((b as LinqValue)._value as JValue).Value;
            }

            JValue value = a._value as JValue;

            if (value.Type == JTokenType.Integer && ConvertUtils.IsInteger(b))
                return value.Value<long>() > Convert.ToInt64(b);
            else if ((value.Type == JTokenType.Integer || value.Type == JTokenType.Float) && ConvertUtils.IsNumber(b))
                return value.Value<decimal>() > Convert.ToDecimal(b);
            else if (value.Type == JTokenType.Date && b is DateTime)
                return value.Value<DateTime>() > (DateTime)b;
            else if (value.Type == JTokenType.TimeSpan && b is TimeSpan)
                return value.Value<TimeSpan>() > (TimeSpan)b;
            else
                return false;
        }

        private static bool LessThen(LinqValue a, object b)
        {
            bool aIsNull = isNull(a);
            bool bIsNull = isNull(b);
            if (aIsNull || bIsNull)
                return false;

            if (!(a._value is JValue))
                return false;

            if (b is JValue)
                b = (b as JValue).Value;
            else if (b is LinqValue)
            {
                if (!((b as LinqValue)._value is JValue))
                    return false;
                b = ((b as LinqValue)._value as JValue).Value;
            }

            JValue value = a._value as JValue;

            if (value.Type == JTokenType.Integer && ConvertUtils.IsInteger(b))
                return value.Value<long>() < Convert.ToInt64(b);
            else if ((value.Type == JTokenType.Integer || value.Type == JTokenType.Float) && ConvertUtils.IsNumber(b))
                return value.Value<decimal>() < Convert.ToDecimal(b);
            else if (value.Type == JTokenType.Date && b is DateTime)
                return value.Value<DateTime>() < (DateTime)b;
            else if (value.Type == JTokenType.TimeSpan && b is TimeSpan)
                return value.Value<TimeSpan>() < (TimeSpan)b;
            else
                return false;
        }

        public override string ToString()
        {
            if (_value == null)
                return "";
            return _value.ToString(Newtonsoft.Json.Formatting.None);
        }

        public override int GetHashCode()
        {
            if (_value == null)
                return 0;

            return _value.GetHashCode();
        }

        public override bool Equals(object b)
        {
            return Equals(this, b);
        }

        public static implicit operator JToken(LinqValue value)
        {
            if (value == null)
                return JValue.CreateNull();
            return value._value;
        }

        public static bool operator ==(LinqValue a, object b)
        {
            return Equals(a, b);
        }

        public static bool operator !=(LinqValue a, object b)
        {
            return !Equals(a, b);
        }

        public static bool operator >(LinqValue a, object b)
        {
            return GreaterThen(a, b);
        }

        public static bool operator <(LinqValue a, object b)
        {
            return LessThen(a, b);
        }

        public static bool operator >=(LinqValue a, object b)
        {
            return GreaterThen(a, b) || Equals(a, b);
        }

        public static bool operator <=(LinqValue a, object b)
        {
            return LessThen(a, b) || Equals(a, b);
        }

        public static LinqValue operator +(LinqValue a, object b)
        {
            bool aIsNull = isNull(a);
            bool bIsNull = isNull(b);
            if (bIsNull)
                return a;
            else if (aIsNull)
            {
                if (b is LinqValue)
                    return b as LinqValue;
                else
                    return new LinqValue(LinqHelper.ToJToken(b));
            }

            if (b is LinqValue)
            {
                b = (b as LinqValue)._value;
            }

            if (a._value is JContainer)
            {
                var mergeSettings = new JsonMergeSettings()
                {
                    MergeArrayHandling = MergeArrayHandling.Merge,
                    MergeNullValueHandling = MergeNullValueHandling.Ignore,
                    PropertyNameComparison = StringComparison.OrdinalIgnoreCase
                };
                var clone = a._value.DeepClone();
                ((JContainer)clone).Merge(b, mergeSettings);
                return new LinqValue(clone);
            }
            else if (a._value is JValue)
            {
                if (b is JValue)
                    b = (b as JValue).Value;

                JValue value = a._value as JValue;
                if (value.Type == JTokenType.Integer && ConvertUtils.IsInteger(b))
                    return new LinqValue(new JValue(value.Value<long>() + Convert.ToInt64(b)));
                else if ((value.Type == JTokenType.Integer || value.Type == JTokenType.Float) && ConvertUtils.IsNumber(b))
                    return new LinqValue(new JValue(value.Value<decimal>() + Convert.ToDecimal(b)));
                else if (value.Type == JTokenType.TimeSpan && b is TimeSpan)
                    return new LinqValue(new JValue(value.Value<TimeSpan>() + (TimeSpan)b));
                else if (value.Type == JTokenType.String)
                {
                    PrimitiveTypeCode typeCode = ConvertUtils.GetTypeCode(b.GetType());
                    if (typeCode != PrimitiveTypeCode.Empty && typeCode != PrimitiveTypeCode.Object)
                        return new LinqValue(new JValue(value.Value<string>() + b.ToString()));
                }
            }
            return a;
        }

        public static LinqValue operator -(LinqValue a, object b)
        {
            bool aIsNull = isNull(a);
            bool bIsNull = isNull(b);
            if (aIsNull || bIsNull)
                return a;

            if (b is LinqValue)
            {
                b = (b as LinqValue)._value;
            }

            if (a._value is JValue)
            {
                if (b is JValue)
                    b = (b as JValue).Value;

                JValue value = a._value as JValue;
                if (value.Type == JTokenType.Integer && ConvertUtils.IsInteger(b))
                    return new LinqValue(new JValue(value.Value<long>() - Convert.ToInt64(b)));
                else if ((value.Type == JTokenType.Integer || value.Type == JTokenType.Float) && ConvertUtils.IsNumber(b))
                    return new LinqValue(new JValue(value.Value<decimal>() - Convert.ToDecimal(b)));
                else if (value.Type == JTokenType.TimeSpan && b is TimeSpan)
                    return new LinqValue(new JValue(value.Value<TimeSpan>() - (TimeSpan)b));
            }
            return a;
        }

        public static LinqValue operator *(LinqValue a, object b)
        {
            bool aIsNull = isNull(a);
            bool bIsNull = isNull(b);
            if (aIsNull || bIsNull)
                return a;

            if (b is LinqValue)
            {
                b = (b as LinqValue)._value;
            }

            if (a._value is JValue)
            {
                if (b is JValue)
                    b = (b as JValue).Value;

                JValue value = a._value as JValue;
                if (value.Type == JTokenType.Integer && ConvertUtils.IsInteger(b))
                    return new LinqValue(new JValue(value.Value<long>() * Convert.ToInt64(b)));
                else if ((value.Type == JTokenType.Integer || value.Type == JTokenType.Float) && ConvertUtils.IsNumber(b))
                    return new LinqValue(new JValue(value.Value<decimal>() * Convert.ToDecimal(b)));
            }
            return a;
        }

        public static LinqValue operator /(LinqValue a, object b)
        {
            bool aIsNull = isNull(a);
            bool bIsNull = isNull(b);
            if (aIsNull || bIsNull)
                return a;

            if (b is LinqValue)
            {
                b = (b as LinqValue)._value;
            }

            if (a._value is JValue)
            {
                if (b is JValue)
                    b = (b as JValue).Value;

                JValue value = a._value as JValue;
                if (value.Type == JTokenType.Integer && ConvertUtils.IsInteger(b))
                    return new LinqValue(new JValue(value.Value<long>() / Convert.ToInt64(b)));
                else if ((value.Type == JTokenType.Integer || value.Type == JTokenType.Float) && ConvertUtils.IsNumber(b))
                    return new LinqValue(new JValue(value.Value<decimal>() / Convert.ToDecimal(b)));
            }
            return a;
        }
    }
}
